Technical Note TN2065
AppleScript での do shell script

 目次

このテクニカルノートでは、AppleScript 1.8 に導入された AppleScript の do shell script コマンドの基本テクニックを説明するとともに、よくある質問とその回答を紹介します。

[2003 年 1 月 27 日]






コマンドの発行

Q: Terminal では正常に動作するコマンドが、do shell script で使おうとすると、「command not found(コマンドが見つかりません)」というエラーが表示されます。

A: 2 つの可能性があります。1 つは、do shell script は常に sh シェルを使用し、Terminal が使用するデフォルトのシェルは使用しないということです。(新しいユーザ用のデフォルトのシェルは csh で、変更しない限りこのシェルが使われます)。コマンドによっては、シェル間で同じ動作をするものと、そうでないものがあり、動作が異なるコマンドの 1 つを使用した可能性があります。do shell script スクリプトを最初に Terminal で書く場合は、必ず sh を使用してください。「/bin/sh」と入力すると、sh を起動できます。通常のシェルに戻るには、「exit」と入力します。

もう 1 つは、コマンド名だけを使用した場合、シェルは(PATH として知られる)ディレクトリのリストを使って、そのコマンドの完全パスを探そうとします。セキュリティと移植性の理由から、do shell script は独自のリストを使用するため、そのリストが、デフォルトのシェルが使用するリストと一致しない場合があります。このため、単に ifconfig とするのではなく、/sbin/ifconfig のように、コマンドへの完全パスを使用してください。Terminal で完全パスを検索するには、「which コマンド名」(「which ifconfig」など)と入力し、do shell script が検索する場所のリストを表示するには、「do shell script "echo $PATH"」と入力します。

Q: do shell script は、なぜ Terminal と全く同じように動作しないのですか?

A: 2 つの理由があります。1 つは、スクリプトを、異なるシステムで変更することなく実行できるよう保証するためです。あるユーザのデフォルトシェルや PATH を使った do shell script は、他のユーザの環境では、おそらく実行が中断してしまいます。もう 1 つは、do shell script は Perl などの他の言語のシェルエスケープのメカニズムに合わせているためです。

Q: sh 以外のシェルでコマンドを実行するにはどうしたらよいですか?

A: 使用するシェルを、コマンドで明示的に指定します。選択したシェルにコマンドを渡す方法は、いくつかあります。コマンドをファイルに書き、次のようにファイルを実行する方法もあります。

do shell script "/bin/tcsh my-command-file-path"

いくつかのシェルは、次のようにパラメータとしてスクリプトを受け付けます。

do shell script "/bin/tcsh -c 'my-command'"

また、大部分のシェルは、次のように標準入力からスクリプトを受け付けます。

do shell script "echo my-command | /bin/tcsh"

不明な点については、使用するシェルのドキュメントを参照してください。do shell script 文字列の中でコマンドを使う場合は、おそらくコマンドを引用符で囲む必要があります。そうしないと、sh はコマンドの特別な文字を解釈してしまいます。

Q: 1 つの do shell script で複数のコマンドを使用するにはどうしたらよいですか?たとえば、cd コマンドであるディレクトリに移動してから何らかの操作を行いたいのですが、あるコマンドから別のコマンドに移るときに、作業ディレクトリが記憶されません。

A: do shell script を呼び出すたびに、新しいシェルプロセスが使用されるため、変数や作業ディレクトリの変更などの状態は、ある呼び出しから次の呼び出しに移る際に保存されません。1 回の呼び出しで複数のコマンドを実行するには、次のようにセミコロンでコマンドを区切ります。

do shell script "cd ‾/Documents; ls"
-- result: "Welcome.txt"

認証機能の仕組みに不具合があるため、administrator privileges に関しては、この方法は正しく動作しません。次の Q&A を参照してください。

Q: あるコマンドに関する管理者特権を取得するにはどのようにすればよいですか?

A: 次のように administrator privilegespassword のパラメータを使います。

do shell script "command" password "mypassword" with administrator privileges

password パラメータを省略すると、実行時に do shell script がパスワードを尋ねてきます。

管理者特権があれば、システム中のすべての場所にあるすべてのファイルを変更できることに注意してください。的確なコマンドをいくつか使用するだけで、システムを起動できない状態にしたり、ディスク全体を消去することさえ可能になるため、用心する必要があります。できれば、どうしても必要な場合以外は、管理者特権は使用しないことをお勧めします。システムレベルでの開発を行っている場合を除き、/System 内を変更する必要はまずありません。通常は /Library の変更で十分です。

不具合が原因で、複数のコマンドを使用する場合、administrator privileges は正常に動作しません。次のように、コマンドを sh の単一呼び出しにする必要があります。

set normal_command to "command1; command2"
do shell script "sh -c " & quoted form of normal_command with administrator privileges

先頭に戻る

結果の取得

Q: do shell script はどのようにして結果を取得するのですか?

A: シェルコマンドは、標準出力と標準エラー出力という 2 つの出力ストリームの一方に結果を書き出せます。標準出力は通常の出力に使われ、標準エラー出力はエラーメッセージと診断に使われます。スクリプトが正常に完了したと仮定すると(正常でない場合は次の Q&A を参照)、結果は標準出力に出力されたテキストに、おそらくいくつかの修正が加えられたものになります。

デフォルトでは、do shell script は結果内の行末をすべて Mac 形式の復帰改行(¥r またはコード番号 13 の ASCII 文字)に変換し、(もしあれば)行末の終了文字を 1 つ除去します。つまり、「do shell script "echo foo"」の結果は、"foo" のみで、echo が実際に返した "foo¥n" ではないことを意味します。行末を変更しない(without altering line endings)パラメータを追加すると、この 2 つの動作を両方とも無効にできます。非 ASCII データの扱いについては、テキストの扱いを参照してください。

Q: do shell script はエラーをどのように報告するのですか?

A: シェルコマンドはすべて、完了時に整数のステータスを返します。0 は成功を表し、それ以外は失敗を表します。0 以外のステータスを持つスクリプトがある場合、do shell script は ステータスがエラー番号である AppleScript エラーをスローします。(コマンドの man ページに、そのコマンドがどのステータスコードを返せるかが記載されているはずです。大部分のコマンドは、すべてのエラーに単に 1 を使用します)。スクリプトが標準エラーストリームに対して何かを出力した場合は、そのテキストが AppleScript のエラーメッセージになります。エラーテキストがない場合、(もしあれば)通常の出力がエラーメッセージとして使用されます。

Q: Terminal でコマンドを実行すると一連の出力が表示されるのですが、do shell script を使用すると、その一部は表示されません。

A: Terminal でコマンドを実行する場合、標準出力と標準エラー出力は両方とも同じ場所に送信されるため、それらを見分けることは困難です。これに対して do shell script は、これらの 2 つのストリームを分離したままにします。標準出力と標準エラー出力を結合したい場合は、次のようにコマンドの後に 2>&1 を付けます。

do shell script "command 2>&1"

詳細については、sh の man ページの「Redirections」を参照してください。

先頭に戻る

テキストの扱い

Q: パラメータにスペースや(括弧、$、* などの)区切り文字が含まれていると、コマンドが正しく動作しません。

A: シェルは複数のパラメータをスペースで区切り、いくつかの区切り記号には特殊な意味があるため、スペースや区切り記号などを含む文字列を 1 つのパラメータとしてシェルで処理するには、特別な手順が必要になります。この手順は「クォーティング」と呼ばれ、いくつかの方法がありますが、最も簡単で効果的なのは、文字列の quoted form プロパティを使用する方法です。

たとえば、次の(不完全な)ハンドラについて考えてみます。このハンドラは、文字列を取得し、それをホームディレクトリにある「stuff」というファイルに追加します。

to append_message(s)
    do shell script "echo " & s & " >> ‾/stuff"
end append_message

大部分の文字列に対してはうまくいきますが、「$100」などの文字列を使って呼び出すと、この文字列がファイルに追加されるときには、「00」となってしまいます。これは、シェルが "$1" を、値が空の文字列である変数と解釈するためです。(sh の変数は、先頭にドル記号が付きます)。このスクリプトを修正するには、次のように変更します。

do shell script "echo " & quoted form of s & " >> ‾/stuff"

quoted form プロパティにより、フォームの文字列にはその内容に関係なく、シェルによるそれ以上の解釈はなされません。クォーティングの詳細については、sh の man ページの「Quoting」を参照してください。

Q: シェルコマンドで二重引用符とバックスラッシュを使う必要があるのですが、AppleScript で実行すると構文エラーになります。

A: AppleScript では、文字列は二重引用符で始まり二重引用符で終わります。文字列の中でリテラルとしての二重引用符を使うには、次のようにバックスラッシュ文字を使って、「エスケープ処理」を行う必要があります。

"a ¥"quote¥" mark"

バックスラッシュには、「次の文字を特別に扱う」という意味があります。このため、リテラルのバックスラッシュを使用するには、次のように 2 つのバックスラッシュが必要になります。

"a back¥¥slash"

以上をまとめると、次のようするとうまくいくはずです。

set s to "this is a test."
do shell script "echo " & quoted form of s & " | perl -n -e 'print ¥"¥¥U$_¥"'"
-- result: "THIS IS A TEST."

スクリプトにはバックスラッシュが追加されていますが、Perl の -e オプションに渡される実際の文字列は、次のようになります。

print "¥U$_"

Q: シェルスクリプトが二重引用符やバックスラッシュを返すときには常に、その先頭に余分なバックスラッシュが付いています。

A: 結果ウィンドウでは、スクリプトにペーストしてそのままコンパイルできるように、結果が「ソース」形式で表示されます。つまり、文字列の結果は引用符で囲まれ、二重引用符やバックスラッシュなどの特殊文字は、前述のようにエスケープ処理されるということです。追加されたバックスラッシュは、実際には文字列の一部ではなく、単に表示されているだけです。その文字列を display dialog に渡したり、ファイルに書き込むと、余分なバックスラッシュは表示されません。

Q: do shell script は、非 ASCII テキスト(アクセント記号付き文字や日本語など)をどのように扱いますか?

A: AppleScript 1.8.3 の時点では、do shell script は入出力をすべて UTF-8 として処理します。この処理は、コマンド自体だけでなく、ファイル名に関しても正しく動作します。(以前は、ユーザの主要なエンコードが使われていたため、ファイル名に非 ASCII 文字を含むファイルを扱うのは極めて困難でした)。

現時点では、UTF-8 以外のエンコードはサポートされていません。コマンドが、有効な UTF-8 ではない非 ASCII 文字を生成した場合(たとえば、MacRoman エンコードを使って保存されたテキストファイルを cat する場合など)、do shell script は「can't make some data into the expected type.(いくつかのデータを期待されている種類に変換できない)」というエラーを返します。解決策としては、出力をファイルに書き出し、AppleScript の read コマンドを使うか、vis を通じてパイプ処理を行って読み取る方法があります。

大部分のシェルコマンドは、Unicode と UTF-8 を完全に無視することを考慮してください。ASCII 文字に関しては、UTF-8 と ASCII は類似しています。たとえば、A は、ASCII でも UTF-8 でも 16 進数では 0x41 になります。非 ASCII 文字はバイトの並びとして表されます。ただしシェルコマンドに関する限り、1 バイトは 1 文字に相当し、シェルコマンドは ASCII 範囲外に解釈しようとすることはありません。このことは、シェルコマンドでは UTF-8 シーケンスが保持され、正確にバイト対バイトの照合が行われることを意味します。たとえば、echo "™" はトレードマーク記号を生成し、grep "α" で小文字のアルファ記号が含まれる全ての行が検索されます。ただし、シェルコマンドは UTF-8 シーケンスのソート、変更、比較をインテリジェントには処理できません。たとえば、tr などの文字セット照合コマンドや、sed[] コンストラクトはシーケンスの各バイトを別々に照合し、sort ではアクセント記号付き文字の並べ替え順序が不正確で、grep -ifind -iname は、éÉ の照合を行いません。Perl は、このような混乱が生じない例外として有名ですが、通常 Perl スクリプトには use utf8 を追加する必要があります。詳細については、perlunicode の man ページを参照してください。

Q: 行末に関してはどのような規則がありますか?

A: Mac OS X では、行末には 2 種類の規則があります。Mac 形式(行は復帰、すなわち ¥r またはコード番号 13 の ASCII 文字で終了)と、UNIX 形式(行は改行、すなわち ¥n またはコード番号 10 の ASCII 文字で終了)です。シェルコマンドは通常、UNIX 形式の行末のみを処理するため、Mac 形式のテキストを渡すと、そのままでは使用できない結果が得られます。たとえば、grep では入力全体には 1 行しかないとみなされるため、一致は多くても 1 つになります。

データが AppleScript のものであれば、行末を変換するか、先頭位置に改行を生成することができます。改行は、¥nコード番号 10 の ASCII 文字で生成されます。データがファイルからのものである場合は、tr を使って、シェルスクリプトで改行コードを変換できます。次の例では、プレーンテキストファイルで "something" が含まれる行が検索されます(「quoted form of POSIX path of f」については、ファイルの扱いで説明されています)。

set f to choose file
do shell script "tr '¥¥r' '¥¥n' < " & quoted form of POSIX path of f & " | grep something"

AppleScript 自体は行末の種類を区別しません。string オブジェクトと Unicode text オブジェクトの paragraph エレメントは、Mac、UNIX、Windows の行末を同じものと見なします。全般的に、UNIX 形式の行を取得するのにテキスト項目区切り文字(text item delimiters)を使う必要はありません。paragraph nevery paragraph は適切に動作します。(ただし、UNIX 形式の行末だけを考慮する場合は、text item delimiters を使うのが適切な解決策です。また、AppleScript 1.9.1 より以前では、復帰と Unicode のパラグラフ区切り文字をパラグラフの区切りと見なしていたのは、Unicode text オブジェクトのみです)。

先頭に戻る

ファイルの扱い

Q: AppleScript の file オブジェクトまたは alias オブジェクトを取得した後、これをシェルコマンドに渡すにはどのようにすればよいですか?

A: シェルは、POSIX パス名、すなわちパスの要素がスラッシュで区切られた文字列を使ってファイルを指定します(たとえば、"/folder1/folder2/file")。AppleScript の file オブジェクトや alias オブジェクトの POSIX パス名を取得するには、POSIX path プロパティを使います(ただし、次の Q&A を参照してください)。次に例を示します。

POSIX path of file "HD:Users:me:Documents:Welcome.txt"
-- result:"/Users/me/Documents/Welcome.txt"

たとえば、シェルコマンドが結果として POSIX パスを返す場合は、POSIX file オブジェクトを使用するもう 1 つの方法があります。パス名を付けた POSIX file は、通常の file オブジェクトとして評価され、他の AppleScript コマンドに渡せます。次に例を示します。

set p to do shell script "echo ‾"
POSIX file p
-- result:file "HD:Users:me:"

Q: ファイル名に、スペース、括弧、$、* などの文字が含まれていると、POSIX path が正しく動作しません。

A: これはクォーティングの特殊ケースです。シェルがすべての区切り文字をリテラルとして解釈できるようにするには、パスを引用符で囲む必要があります。これを行うには、パスの quoted form を使います。たとえば、次の例では、名前に関係なく、どのようなファイルにも正しく動作します。

choose file
do shell script "ls -l " & quoted form of the POSIX path of the result
-- result:"-rw-r--r-- 1 me unknown 1 Oct 25 17:48 Look! a file!"

Q: POSIX path がすべてを引用符で囲まないのはなぜですか?

A: 2 つの理由があります。第 1 に、POSIX path には、シェルパラメータとはまったく関係のない使用法があり、そのようなケースでは、パスを引用符で囲むことは適切ではありません。第 2 に、quoted form は、ファイルパス以外のものに対しても効果があります。このため、POSIX path はすべてを引用符で囲みません。

先頭に戻る

その他の問題点

Q: ftptelnet などの対話型ツールを do shell script で制御するにはどのようにすればよいですか?

A: 簡単にいうと、方法はありません。do shell script は、コマンドを開始し、コマンドが完了するまでは対話なしで実行するように設計されています。多くの UNIX シェルのバッククォート演算子や、awk と Perl の system 呼び出しによく似ています。

ただし回避策はあります。Terminal のスクリプトを作成し、同じウィンドウへ一連のコマンドを送信する方法(ただし Mac OS X 10.2 以降のみ)や、対話型ツールのスクリプティング用の UNIX パッケージ(expect など)を使う方法があります。また、多くの対話型コマンドには、非対話型の同等のコマンドがあります。たとえば、多くの場合 curlftp の代わりになります。

Q: スクリプトの出力に非常に時間がかかります。結果が出た時点でそれを読むにはどうすればよいですか?

A: これについても、方法はありません。do shell script は、コマンドが完了するまで戻ってきません。ただし、対処法としては、コマンドをバックグラウンドで実行し(次の Q&A を参照)、出力をファイルに送信し、ファイルが完成したらファイルを読むという方法があります。

Q: バックグラウンドサーバプロセスを開始したい場合、do shell script にコマンドの完了を待機させないようにするにはどうすればよいですか?

A: 「do shell script "command > file_path 2>&1 &"」を使います。do shell script は、結果なしで直ちに戻り、AppleScript スクリプトはシェルスクリプトと並行して実行します。シェルスクリプトの出力先は file_path になります。出力に関心がない場合は、"/dev/null" を使用します。AppleScript からのバックグラウンドプロセスの取得や操作は、直接的にはサポートされていません。

Q: top を使おうとすると、「can't get terminal attributes(ターミナル属性を取得できません)」または「error opening terminal: unknown(「ターミナルを開けません : 不明」)」というエラーになります。

A: top は、デフォルトのモードではダイナミックに更新される表示画面を作成するために、さまざまな賢い動作をしますが、do shell script がカーソルコントロールをサポートしていないのと同じように、出力デバイスがカーソルコントロールをサポートしてない場合は、こうした動作はうまく機能しません。しかし、top にはロギングモードで動作するオプションがあり、do shell script のようなファイルに似たデバイスに対しては正常に動作します。代わりに top -l1 を使うか、top の man ページで、他のオプションについても参照してください。

この問題は、ターミナルがあることを想定している他のすべてのコマンドにも当てはまります。幸い、その大部分は、ターミナルを前提としていない比較的単純なコマンドへの、対話型のフロントエンドです。

Q: do shell script コマンドのデフォルトの作業ディレクトリはどこですか?

A: do shell script は、親プロセスの作業ディレクトリを継承します。Script Editor などの大部分のアプリケーションでは、親プロセスである Finder の作業ディレクトリの "/" になります。osascript では、osascript を起動した時点での、シェルの作業ディレクトリになります。特定のディレクトリが必要である場合は、デフォルトの作業ディレクトリを使用しないでください。作業ディレクトリを特定の別の場所にする必要がある場合は、自分自身で設定する必要があります。

先頭に戻る

ダウンロード

Acrobat gif

このテクニカルノートの PDF 版(152K)

ダウンロード


先頭に戻る